package pl.edu.mimuw.irs.core.model.design.net;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.design.atomic.AtomicSubnet;
import pl.edu.mimuw.irs.core.model.design.atomic.INodeFactory;
import pl.edu.mimuw.irs.core.model.design.exception.InternalDesignException;
import pl.edu.mimuw.irs.core.model.design.exception.InternalDesignException.DesignCause;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Net;
import pl.edu.mimuw.irs.core.model.petrinet.Place;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Converter;
import pl.edu.mimuw.irs.core.util.Position;

/**
 * @author Jakub Rauch Created on: 2009-05-07
 */
public class IRSPlace extends Place implements IRSNode {

	private static final long serialVersionUID = 4744674617764045683L;

	// *** Attributes

	private NodeType nodeType;
	private Fragmentation fragmentation;
	private IRSNode parent;
	private Position customPosition;
	// Children should be in specific order - intuitive according to
	// fragmentations.
	private List<IRSNode> children;
	// Node supports
	private static final Set<Fragmentation> supportedFragmentations;
	static {
		supportedFragmentations = new LinkedHashSet<Fragmentation>();
		supportedFragmentations.add(Fragmentation.SEQUENCE);
		supportedFragmentations.add(Fragmentation.CONCURRENCY);
		supportedFragmentations.add(Fragmentation.ITERATION);
		supportedFragmentations.add(Fragmentation.NONE);
	}
	private boolean expanded = false;

	// Getters and setters
	public NodeType getNodeType() {
		if (nodeType == null)
			throw new InternalDesignException(DesignCause.UNKNOWN_NODE_TYPE);
		return nodeType;
	}
	public void setNodeType(NodeType nodeType) {
		this.nodeType = nodeType;
	}
	public Fragmentation getFragmentation() {
		if (fragmentation == null)
			fragmentation = Fragmentation.NONE;
		return fragmentation;
	}
	public void setFragmentation(Fragmentation fragmentation) {
		if (fragmentation == null)
			fragmentation = Fragmentation.NONE;
		if (!getSupportedFragmentations().contains(fragmentation))
			throw new InternalDesignException(
					DesignCause.UNSUPPORTED_FRAGMENTATION_TYPE, fragmentation);
		Fragmentation oldFragmentation = getFragmentation();
		handleFragmentation(fragmentation);
		getListeners().firePropertyChange(FRAGMENTATION, oldFragmentation,
				fragmentation);
	}
	public IRSNode getParent() {
		return parent;
	}
	public void setParent(IRSNode parent) {
		this.parent = parent;
	}
	@Override
	public Position getPosition() {
		return this.customPosition == null ? super.getPosition() : getCustomPosition(); 
	}
	public Position getCustomPosition() {
		return customPosition;
	}
	public void setCustomPosition(Position customPosition) {
		this.customPosition = customPosition;
		setPosition(customPosition);
	}
	public Position getSuperPosition() {
		return super.getPosition();
	}
	
	/**
	 * List returned by this method should NOT be modified.
	 */
	public List<IRSNode> getChildren() {
		if (children == null)
			children = new ArrayList<IRSNode>();
		return children;
	}

	public void setChildren(List<IRSNode> children, Fragmentation fragmentation) {
		removeChildren(children);
		for (IRSNode child : children) {
			if (!child.isDetached())
				addChild(child);
		}
		Fragmentation oldFragmentation = this.fragmentation;
		this.fragmentation = fragmentation;
		getListeners().firePropertyChange(FRAGMENTATION, oldFragmentation,
				fragmentation);
	}

	public void addChild(int index, IRSNode node) {
		this.getChildren().add(index, node);
		node.setParent(this);
	}

	public void addChild(IRSNode node) {
		this.getChildren().add(node);
		node.setParent(this);
	}

	public void removeChild(IRSNode node) {
		this.getChildren().remove(node);
		node.detach();
	}

	@Override
	public boolean isRoot() {
		return getParent() == null;
	}

	public boolean isAtomic() {
		return this.getFragmentation().equals(Fragmentation.NONE);
	}

	public boolean isInPlace() {
		return getIncomingArcs().size() == 0;
	}

	public boolean isOutPlace() {
		return getOutgoingArcs().size() == 0;
	}

	public Set<Fragmentation> getSupportedFragmentations() {
		if (!isInPlace() && !isOutPlace())
			return supportedFragmentations;
		else {
			Set<Fragmentation> tmpFragmentations = new LinkedHashSet<Fragmentation>();
			tmpFragmentations.add(Fragmentation.NONE);
			tmpFragmentations.add(Fragmentation.SEQUENCE);
			return tmpFragmentations;
		}
	}

	public boolean isExpanded() {
		return expanded && !getFragmentation().equals(Fragmentation.NONE);
	}

	public void setExpanded(boolean expand) {

//		List<IRSNode> children = getChildren();
		boolean oldExpanded = this.expanded;

//		if (children.size() > 0) {
//			List<Position> positions = new LinkedList<Position>();
//			for (IRSNode node : getChildren()) {
//				positions.add(node.getPosition());
//			}
//			// for (IRSNodeInterface node : getExpandedNodes()) {
//			// positions.add(node.getPosition());
//			// }
//			Position[] positionsArray = new Position[positions.size()];
//			Position avgPosition = Position.getAverage(positions
//					.toArray(positionsArray));
//
//			if (expand) {
//				Position diff = this.getPosition().sub(avgPosition);
//				for (IRSNode node : children) {
//					node.setPosition(node.getPosition().sum(diff));
//				}
//				this.expanded = expand;
//			} else {
//				this.setPosition(avgPosition);
//				this.expanded = expand;
//			}
//		} else {
			if (expand) {
				this.expanded = expand;
			} else {
				this.expanded = expand;
			}
//		}

		getListeners().firePropertyChange(EXPANDED, oldExpanded, expanded);
		for (IRSNode child : getChildren()) {
			child.getListeners().firePropertyChange(VISIBLE, oldExpanded,
					expanded);
		}
	}

	@Override
	public IRSPlaceCopier getCopier() {
		return new IRSPlaceCopier(this);
	}

	// *** Constructors

	public IRSPlace(IDManager idManager, String id, NodeType nodeType,
			Fragmentation fragementation, Position position, int tokens) {
		super(idManager, id, position, tokens);
		setNodeType(nodeType);
		setFragmentation(fragementation);
	}

	public IRSPlace(IDManager idManager, String id, NodeType nodeType,
			Position position, int tokens) {
		this(idManager, id, nodeType, null, position, tokens);
	}

	public IRSPlace(IDManager idManager, String id, Position position,
			int tokens) {
		this(idManager, id, NodeType.PLACE, position, tokens);
	}

	public IRSPlace(IDManager idManager, String id, Position position) {
		this(idManager, id, position, 0);
	}

	public IRSPlace(IDManager idManager, Position position) {
		this(idManager, null, position);
	}

	public IRSPlace(IDManager idManager, String id) {
		this(idManager, id, new Position());
	}

	public IRSPlace(IDManager idManager) {
		this(idManager, (String) null);
	}

	// *** Methods

	// Invoked on fragmentation change - causes node structural change.
	private void handleFragmentation(Fragmentation fragmentation) {
		switch (fragmentation) {
		case NONE:
			removeChildren();
			break;
		case SEQUENCE:
			setSequence(fragmentation);
			for (int i = 0; i < getChildren().size(); i++) getChildren().get(i).setName(getName() + (i + 1)); // TODO : rm
			break;
		case CONCURRENCY:
			setConcurrency(fragmentation);
			for (int i = 0; i < getChildren().size(); i++) getChildren().get(i).setName(getName() + (i + 1));
			break;
		case ITERATION:
			setIteration(fragmentation);
			for (int i = 0; i < getChildren().size(); i++) getChildren().get(i).setName(getName() + (i + 1));
			break;
		default:
			throw new InternalDesignException(
					DesignCause.UNSUPPORTED_FRAGMENTATION_TYPE, fragmentation);
		}

	}

	// Fragmentation private methods

	private void setSequence(Fragmentation fragmentation) {
		IDManager idManager = getIdManager();
		List<IRSNode> sequence = new LinkedList<IRSNode>();
		Net net = getNet();

		IRSPlace firstPlace = Converter
				.asIRSPlace(copy(IRSNodeCopier.CLONE_INCOMING_ARCS));
		IRSPlace lastPlace = Converter
				.asIRSPlace(copy(IRSNodeCopier.CLONE_OUTGOING_ARCS));
		IRSTransition lastTransition = new IRSTransition(idManager, this
				.getPosition());
		net.addTransition(lastTransition);
		sequence.add(firstPlace);
		sequence.add(lastTransition);
		IRSArc firstArc = new IRSArc(idManager, firstPlace, lastTransition);
		net.addArc(firstArc);
		getCopier().propagate(firstArc);
		sequence.add(lastPlace);
		IRSArc lastArc = new IRSArc(idManager, lastTransition, lastPlace);
		net.addArc(lastArc);
		getCopier().propagate(lastArc);

		setChildren(sequence, fragmentation);
	}

	private void setConcurrency(Fragmentation fragmentation) {
		List<IRSNode> nodes = new ArrayList<IRSNode>();

		nodes.add(copy());
		nodes.add(copy());

		setChildren(nodes, fragmentation);
	}

	private void setIteration(Fragmentation fragmentation) {
		Net net = getNet();

		IRSPlace newPlace = Converter.asIRSPlace(copy());
		IRSTransition newTransition = new IRSTransition(getIdManager(), getPosition());
		removeChildren();
		net.addTransition(newTransition);
		net.addArc(new IRSArc(getIdManager(), newPlace, newTransition));
		net.addArc(new IRSArc(getIdManager(), newTransition, newPlace));

		/* Creating iteration arcs for descendants. */
		for (IRSNode descendant : newPlace.getInputDescendants()) {
			IRSPlace place = Converter.asIRSPlace(descendant);
			net.addArc(new IRSArc(getIdManager(), newTransition, place));
		}
		for (IRSNode descendant : newPlace.getOutputDescendants()) {
			IRSPlace place = Converter.asIRSPlace(descendant);
			net.addArc(new IRSArc(getIdManager(), place, newTransition));
		}

		addChild(newPlace);
		addChild(newTransition);

		this.fragmentation = fragmentation;
	}

	public List<IRSNode> getAtomicNodes() {
		List<IRSNode> leafs = new LinkedList<IRSNode>();
		if (getFragmentation().equals(Fragmentation.NONE)) {
			leafs.add(this);
		} else {
			for (IRSNode node : getChildren()) {
				IRSNode rcpNode = (IRSNode) node;
				leafs.addAll(rcpNode.getAtomicNodes());
			}
		}
		return leafs;
	}

	public List<IRSNode> getExpandedNodes() {
		List<IRSNode> leafs = new LinkedList<IRSNode>();
		if (!isExpanded()) {
			leafs.add(this);
			return leafs;
		} else {
			for (IRSNode node : getChildren()) {
				IRSNode rcpNode = (IRSNode) node;
				leafs.addAll(rcpNode.getExpandedNodes());
			}
		}
		return leafs;
	}

	public AtomicSubnet getAtomicWorkflow(INodeFactory factory) {
		return getAtomicWorkflow(new AtomicSubnet(factory));
	}

	public Place copy(AtomicSubnet atomicSubnet) {
		return atomicSubnet.getFactory().copyPlace(this);
	}

	public AtomicSubnet getAtomicWorkflow(AtomicSubnet atomicSubnet) {
		if (Fragmentation.NONE.equals(getFragmentation())) {
			// XXX : wiecej szczegółów miejsca
			Place place = copy(atomicSubnet);
			atomicSubnet.addPlace(place);
			atomicSubnet.getPlacesMap().put(this, place);
			Map<IRSTransition, Transition> transitionsMap = atomicSubnet
					.getTransitionsMap();
			for (IRSArc arc : getAtomicIncomingArcs()) {
				Transition transition = transitionsMap.get(Converter
						.asIRSTransition(arc.getTransition()));
				if (transition != null) {
					atomicSubnet.addArc(transition, place, arc.getWeight());
				}
			}
			for (IRSArc arc : getAtomicOutgoingArcs()) {
				Transition transition = transitionsMap.get(Converter
						.asIRSTransition(arc.getTransition()));
				if (transition != null) {
					atomicSubnet.addArc(place, transition, arc.getWeight());
				}
			}
		} else {
			for (IRSNode node : getChildren()) {
				atomicSubnet = node.getAtomicWorkflow(atomicSubnet);
			}
		}
		return atomicSubnet;
	}

	@Override
	public List<IRSArc> getAtomicIncomingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getIncomingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSTransition transition = Converter.asIRSTransition(irsArc
					.getTransition());
			if (transition.isAtomic())
				atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}

	@Override
	public List<IRSArc> getAtomicOutgoingArcs() {
		List<IRSArc> atomicArcs = new LinkedList<IRSArc>();
		for (Arc arc : getOutgoingArcs()) {
			IRSArc irsArc = Converter.asIRSArc(arc);
			IRSTransition transition = Converter.asIRSTransition(irsArc
					.getTransition());
			if (transition.isAtomic())
				atomicArcs.add(irsArc);
		}
		return atomicArcs;
	}

	public IRSNode copy() {
		return getCopier().copy();
	}

	public IRSNode copy(int options) {
		return getCopier().copy(options);
	}

	public IRSNode copyAsResourceSet() {
		return getCopier().copyAsRequirement();
	}

	public void detach() {
		super.detach();
		for (IRSNode child : getChildren()) {
			child.detach();
		}
	}

	@Override
	public List<IRSNode> getInputDescendants() {
		List<IRSNode> inputDescendants = new LinkedList<IRSNode>();
		switch (getFragmentation()) {
		case NONE:
			break;
		case SEQUENCE:
			inputDescendants.add(getChildren().get(0));
			inputDescendants.addAll(getChildren().get(0).getInputDescendants());
			break;
		case CONCURRENCY:
			for (IRSNode child : getChildren()) {
				inputDescendants.add(child);
				inputDescendants.addAll(child.getInputDescendants());
			}
			break;
		case ITERATION:
			inputDescendants.add(getChildren().get(0));
			inputDescendants.addAll(getChildren().get(0).getInputDescendants());
			break;
		default:
			throw new InternalDesignException(
					DesignCause.UNSUPPORTED_FRAGMENTATION_TYPE,
					getFragmentation());
		}
		return inputDescendants;
	}

	@Override
	public List<IRSNode> getOutputDescendants() {
		List<IRSNode> outputDescendants = new LinkedList<IRSNode>();
		switch (getFragmentation()) {
		case NONE:
			break;
		case SEQUENCE:
			outputDescendants.add(getChildren().get(getChildren().size() - 1));
			outputDescendants.addAll(getChildren()
					.get(getChildren().size() - 1).getOutputDescendants());
			break;
		case CONCURRENCY:
			for (IRSNode child : getChildren()) {
				outputDescendants.add(child);
				outputDescendants.addAll(child.getOutputDescendants());
			}
			break;
		case ITERATION:
			outputDescendants.add(getChildren().get(0));
			outputDescendants.addAll(getChildren().get(0)
					.getOutputDescendants());
			break;
		default:
			throw new InternalDesignException(
					DesignCause.UNSUPPORTED_FRAGMENTATION_TYPE,
					getFragmentation());
		}
		return outputDescendants;
	}

	private void removeChildren(Collection<IRSNode> ommit) {
		List<IRSNode> list = new LinkedList<IRSNode>();
		for (IRSNode node : getChildren()) {
			if (!ommit.contains(node))
				list.add(node);
		}
		for (IRSNode node : list) {
			node.detach();
		}
		this.children = new ArrayList<IRSNode>();
		this.fragmentation = Fragmentation.NONE;
	}

	private void removeChildren() {
		removeChildren(new LinkedList<IRSNode>());
	}

	@Override
	public boolean isAncestorOf(IRSNode node) {
		IRSNode parent = node.getParent();
		return (parent != null) && (parent == this || isAncestorOf(parent));
	}
}
